/*
 * Continuous alloc manager.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Aeolus <weiqing.yan@ingenic.com>
 */

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

#include <imp/imp_log.h>

#include "continuous_alloc.h"

#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif

#define TAG "Continuous Alloc"

#define ALREADY_INIT_CODE 0x5a5a5a5a
#define INIT_CODE_SIZE sizeof(ALREADY_INIT_CODE)

#define CONTINUOUS_BLOCK_SIZE (1 << 8)

#define ALIGN_ADDR(base) (((base)+((CONTINUOUS_BLOCK_SIZE)-1))&~((CONTINUOUS_BLOCK_SIZE)-1))
#define ALIGN_SIZE(size) (((size)+((CONTINUOUS_BLOCK_SIZE)-1))&~((CONTINUOUS_BLOCK_SIZE)-1))

typedef struct {
	uint32_t base;
	int size;
} ContinuousInfo;

struct ContinuousBlockInfo;
typedef struct ContinuousBlockInfo ContinuousBlockInfo;
struct ContinuousBlockInfo{
	ContinuousBlockInfo *prev; /* prev */
	ContinuousBlockInfo *next; /* next */
	uint32_t addr;
	int size;
	int used;
};

/*static ContinuousInfo g_continuous = {0};*/
static ContinuousBlockInfo *head = NULL;

static ContinuousBlockInfo *continuous_request_block(ContinuousBlockInfo *info, int size)
{
	ContinuousBlockInfo *t_info = info;
	size = ALIGN_SIZE(size);
	while(1) {
		if((t_info->used == 0) && (size <= t_info->size)) {
			if(size == t_info->size) {
				t_info->used = 1;
				return t_info;
			}

			ContinuousBlockInfo *request_info =
				(ContinuousBlockInfo *)malloc(sizeof(ContinuousBlockInfo));
			if(request_info == NULL) {
				IMP_LOG_ERR(TAG, "%s malloc info error\n", __func__);
				return NULL;
			}

			request_info->prev = t_info;
			request_info->next = t_info->next;
			request_info->addr = t_info->addr + size;
			request_info->size = t_info->size - size;
			request_info->used = 0;

			t_info->next->prev = request_info;
			t_info->next = request_info;
			t_info->size = size;
			t_info->used = 1;

			return t_info;
		}

		t_info = t_info->next;
		if(t_info == info) {
			break;
		}
	}

	IMP_LOG_ERR(TAG, "space isn't enough, t_info:used=%d,addr=0x%x,size=%d\n", t_info->used, t_info->addr, t_info->size);
	return NULL;
}

static int continuous_release_block(ContinuousBlockInfo *info, void *addr)
{
	ContinuousBlockInfo *t_info = info;
	while(1) {
		if((t_info->used == 1) && (t_info->addr == (uint32_t)addr)) {
			t_info->used = 0;

			if((t_info->next->used == 0) && (t_info->next != info)) {
				ContinuousBlockInfo *t_next = t_info->next;
				t_info->size += t_next->size;
				t_info->next = t_next->next;
				t_next->next->prev = t_info;
				free((void *)t_next);
			}

			if((t_info->prev->used == 0) && (t_info != info)) {
				ContinuousBlockInfo *t_prev = t_info->prev;
				t_prev->size += t_info->size;
				t_prev->next = t_info->next;
				t_info->next->prev = t_prev;

				free((void *)t_info);
			}

			return 0;
		}

		t_info = t_info->next;
		if(t_info == info)
			return -1;
	}

	return 0;
}

static void continuous_dump_info(ContinuousBlockInfo *info)
{
	ContinuousBlockInfo *info_tmp = info;
	unsigned long long used_size = 0;
	unsigned long long free_size = 0;
	IMP_LOG_INFO(TAG, "\n================ continuous dump info ============\n");
	while(1) {
		IMP_LOG_INFO(TAG, "\ninfo->addr = 0x%08x\n", info_tmp->addr);
		IMP_LOG_INFO(TAG, "info->size = %d\n", info_tmp->size);
		IMP_LOG_INFO(TAG, "info->used = %d\n", info_tmp->used);

		if(info_tmp->used == 1) {
			used_size += info_tmp->size;
		} else {
			free_size += info_tmp->size;
		}

		info_tmp = info_tmp->next;
		if(info_tmp == info)
			break;
	}
	IMP_LOG_INFO(TAG, "\nMem Total Size: %d\n", (used_size + free_size));
	IMP_LOG_INFO(TAG, "Mem Used  Size: %d\n", used_size);
	IMP_LOG_INFO(TAG, "Mem Free  Size: %d\n", free_size);
	IMP_LOG_INFO(TAG, "Mem Usage Rate: %lld%%\n", (used_size*100)/(free_size + used_size));
	IMP_LOG_INFO(TAG, "\n=============== continuous dump info End ============\n");
}

static void continuous_dump_info_to_file(ContinuousBlockInfo *info)
{
	ContinuousBlockInfo *info_tmp = info;
	unsigned long long used_size = 0;
	unsigned long long free_size = 0;
	char buf[512] = "";
	int len = 0;

	FILE *fd = fopen("/tmp/continuous_mem_info", "w");
	if(fd == NULL) {
		IMP_LOG_ERR(TAG, "Open /tmp/continuous_mem_info failed\n");
		return;
	}

	len = sprintf(buf, "\n================ continuous dump info ============\n");
	fwrite(buf, len, 1, fd);

	while(1) {
		memset(buf, 0x0, sizeof(buf));
		len = sprintf(buf, "\ninfo->addr = 0x%08x\n", info_tmp->addr);
		len += sprintf(buf+len, "info->size = %d\n", info_tmp->size);
		len += sprintf(buf+len, "info->used = %d\n", info_tmp->used);
		fwrite(buf, len, 1, fd);

		if(info_tmp->used == 1) {
			used_size += info_tmp->size;
		} else {
			free_size += info_tmp->size;
		}

		info_tmp = info_tmp->next;
		if(info_tmp == info)
			break;
	}

	memset(buf, 0x0, sizeof(buf));
	len = sprintf(buf, "\nMem Total Size: %lld\n", (used_size + free_size));
	len += sprintf(buf+len, "Mem Used  Size: %lld\n", used_size);
	len += sprintf(buf+len, "Mem Free  Size: %lld\n", free_size);
	len += sprintf(buf+len, "Mem Usage Rate: %lld%%\n", (used_size*100)/(free_size + used_size));
	len += sprintf(buf+len, "\n=============== continuous dump info End ============\n");

	fwrite(buf, len, 1, fd);
	fclose(fd);
}

static int continuous_init_list(void *base, int size)
{
	if(head == NULL) {
		head = (ContinuousBlockInfo *)malloc(sizeof(ContinuousBlockInfo));
		if(head == NULL) {
			IMP_LOG_ERR(TAG, "%s malloc error\n", __func__);
			return -1;
		}
		head->prev = head;
		head->next = head;
		head->addr = (uint32_t)base;
		head->size = size;
		head->used = 0;
	}
	return 0;
}

static int continuous_base_init(void *base, int size)
{
	int ret = 0;

	memset(base, 0, size);
	ret = continuous_init_list(base, size);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "continuous init list error\n");
		return ret;
	}

	return 0;
}

/* Continuous APIs */
int continuous_init(void *base, int size)
{
	int ret = 0;
	uint32_t init_addr = (uint32_t)base;
	int init_size = size;
	if(base == NULL) {
		IMP_LOG_ERR(TAG, "Mem Manager mode init error, base addr is NULL\n");
		return -1;
	}

	if(size < CONTINUOUS_BLOCK_SIZE) {
		IMP_LOG_ERR(TAG, "Mem Manager mode init error, continuous size is too small\n");
		return -1;
	}

	ret = continuous_base_init((void *)init_addr, init_size);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "continuous base init error\n");
		return ret;
	}

	return 0;
}

void *continuous_alloc(int size)
{
	ContinuousBlockInfo *t_info = NULL;

	if(head == NULL) {
		IMP_LOG_ERR(TAG, "continuous memery manager no inited\n");
		return NULL;
	}

	if(size <= 0) {
		IMP_LOG_ERR(TAG, "continuous alloc size(%d) error\n", size);
		return NULL;
	}

	t_info = continuous_request_block(head, size);
	if(t_info == NULL) {
		IMP_LOG_ERR(TAG, "Can't alloc memery: %d\n", size);
		continuous_dump_info(head);
		return NULL;
	}

	return (void *)(t_info->addr);
}

void continuous_free(void *ptr)
{
	int ret = -1;

	if(head == NULL) {
		IMP_LOG_ERR(TAG, "continuous memery manager no inited\n");
		return;
	}

	ret = continuous_release_block(head, ptr);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Can't free memery\n");
		continuous_dump_info(head);
		return;
	}

	return;
}

void continuous_dump()
{
	if(head) {
		continuous_dump_info(head);
	}
}

void continuous_dump_to_file()
{
	if(head) {
		continuous_dump_info_to_file(head);
	}
}

